package com.clp.protocol.iec104.server.pipeline.state.data;

import com.clp.protocol.iec104.apdu.asdu.Cot;
import com.clp.protocol.iec104.apdu.asdu.IAsdu;
import com.clp.protocol.iec104.apdu.asdu.infoobj.InfoObj;
import com.clp.protocol.iec104.apdu.asdu.infoobj.infoelem.C_DC_NA_1_InfoElem;
import com.clp.protocol.iec104.apdu.asdu.infoobj.infoelem.C_SC_NA_1_InfoElem;
import com.clp.protocol.iec104.definition.Tc;
import com.clp.protocol.iec104.definition.TypeTag;
import com.clp.protocol.iec104.definition.cot.Cause;
import com.clp.protocol.iec104.definition.cot.Pn;
import com.clp.protocol.iec104.server.SlaveDataConfig;
import com.clp.protocol.iec104.server.TcToken;
import com.clp.protocol.iec104.server.TcTokenPool;
import com.clp.protocol.iec104.server.pipeline.PipelineManager;
import com.clp.protocol.iec104.server.pipeline.state.AbstractStateHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

@Slf4j
public class TcDataStateHandler extends AbstractDataStateHandler {
    private final TcDataReactor dataReactor;
    private final Map<Integer, TcToken> tokenMap = new HashMap<>();

    public TcDataStateHandler(PipelineManager manager, SlaveDataConfig dataConfig) {
        super(manager);
        this.dataReactor = dataConfig.getTcDataReactor();
    }

    @Override
    protected void resetState() {
        tcTokenPool().returnBack(tokenMap.values());
        tokenMap.clear();
    }

    @Override
    protected void afterResetState() {
    }

    private TcTokenPool tcTokenPool() {
        return inSlaveChannel().getTcTokenPool();
    }

    @Override
    protected IAsdu updateStateByRecv(IAsdu iAsdu) throws Exception {
        Tc.Type tcType = dataReactor.getTcType();
        TypeTag tcTypeTag = tcType.getTypeTag();

        TypeTag typeTag = iAsdu.getTypeTag();
        Cot cot = iAsdu.getCot();
        Cause cause = cot.getCause();

        if (typeTag == TypeTag.C_SC_NA_1) { // 单点遥控
            if (cause != Cause.COT_ACT) return iAsdu;

            // 单点遥控激活
            InfoObj infoObj = iAsdu.getInfoObjs().get(0);
            C_SC_NA_1_InfoElem infoElem = infoObj.getInfoElem().castTo(C_SC_NA_1_InfoElem.class);
            int infoObjAddr = infoObj.getAddr(); // 信息体地址
            Tc.OnePointSwitch onePointSwitch = infoElem.getOnePointSwitch(); // 开关状态
            Tc.CmdType cmdType = infoElem.getCmdType(); // 命令类型

            // 不支持支持处理该遥控类型返回遥控否定
            if (tcTypeTag != typeTag) {
                // 发送否定确认
                iAsduSender().chSendOnePointTcAck(infoObjAddr, cmdType, onePointSwitch, Pn.PN_NO);
                return null; // 丢弃该报文
            }

            if (cmdType == Tc.CmdType.SELECT) { // 选择
                // 是否接受遥控选择
                dataReactor.acceptSelect(inSlaveChannel(), infoObjAddr).whenComplete(new BiConsumer<Boolean, Throwable>() {
                    @Override
                    public void accept(Boolean isAccepted, Throwable throwable) {
                        if (throwable != null) {
                            throwable.printStackTrace();
                            return;
                        }
                        safeExecute(new Runnable() {
                            @Override
                            public void run() {
                                // 检查是否已存在
                                TcToken token = tokenMap.get(infoObjAddr);
                                if (token != null) {
                                    // 已经存在该信息体地址的遥控过程，该过程丢弃
                                    log.warn("[Tc] 遥控点{}的遥控过程已存在，放弃本次遥控选择请求", infoObjAddr);
                                    return;
                                }
                                if (isAccepted) { // 接受选择
                                    // 申请token
                                    token = tcTokenPool().applyFor(infoObjAddr, inSlaveChannel());
                                    if (token == null) {
                                        // 已经存在该信息体地址的别的遥控过程，返回否定确认
                                        iAsduSender().chSendOnePointTcAck(infoObjAddr, cmdType, onePointSwitch, Pn.PN_NO);
                                        return;
                                    }

                                    // 申请成功
                                    tokenMap.put(infoObjAddr, token);
                                    token.setRSelect(Tc.Type.ONE_POINT, onePointSwitch == Tc.OnePointSwitch.ON); // 设置为接收到遥控选择
                                    // 返回肯定确认
                                    iAsduSender().chSendOnePointTcAck(infoObjAddr, cmdType, onePointSwitch, Pn.PN_YES);
                                } else { // 不接受选择
                                    // 否定确认
                                    iAsduSender().chSendOnePointTcAck(infoObjAddr, cmdType, onePointSwitch, Pn.PN_NO);
                                }
                            }
                        });
                    }
                });
            } else { // 执行
                // 是否接受遥控执行
                dataReactor.acceptExecute(inSlaveChannel(), infoObjAddr).whenComplete(new BiConsumer<Boolean, Throwable>() {
                    @Override
                    public void accept(Boolean isAccepted, Throwable throwable) {
                        if (throwable != null) {
                            throwable.printStackTrace();
                            return;
                        }
                        safeExecute(new Runnable() {
                            @Override
                            public void run() {
                                // 检查是否存在
                                TcToken token = tokenMap.get(infoObjAddr);
                                if (token == null) {
                                    // 不存在该信息体地址的遥控过程，该过程丢弃
                                    log.warn("[Tc] 遥控点{}的遥控过程不存在，放弃本次遥控执行请求", infoObjAddr);
                                    return;
                                }
                                if (isAccepted) { // 接受执行
                                    token.setRExecute(Tc.Type.ONE_POINT, onePointSwitch == Tc.OnePointSwitch.ON); // 设置为接收到遥控执行
                                    // 返回肯定确认
                                    iAsduSender().chSendOnePointTcAck(infoObjAddr, cmdType, onePointSwitch, Pn.PN_YES).addListener(new ChannelFutureListener() {
                                        @Override
                                        public void operationComplete(ChannelFuture future) throws Exception {
                                            if (!future.isSuccess()) {
                                                future.cause().printStackTrace();
                                                return;
                                            }
                                            // 肯定确认之后，发送激活终止
                                            iAsduSender().chSendOnePointTcFinished(infoObjAddr, onePointSwitch);
                                        }
                                    });
                                } else { // 不接受选择
                                    // 否定确认
                                    iAsduSender().chSendOnePointTcAck(infoObjAddr, cmdType, onePointSwitch, Pn.PN_NO);
                                }
                            }
                        });
                    }
                });
            }
        } else if (typeTag == TypeTag.C_DC_NA_1) { // 双点遥控
            if (cause != Cause.COT_ACT) return iAsdu;

            // 双点遥控激活
            InfoObj infoObj = iAsdu.getInfoObjs().get(0);
            C_DC_NA_1_InfoElem infoElem = infoObj.getInfoElem().castTo(C_DC_NA_1_InfoElem.class);
            int infoObjAddr = infoObj.getAddr(); // 信息体地址
            Tc.TwoPointSwitch twoPointSwitch = infoElem.getTwoPointSwitch(); // 开关状态
            Tc.CmdType cmdType = infoElem.getCmdType(); // 命令类型

            // 不支持支持处理该遥控类型返回遥控否定
            if (tcTypeTag != typeTag) {
                // 发送否定确认
                iAsduSender().chSendTwoPointTcAck(infoObjAddr, cmdType, twoPointSwitch, Pn.PN_NO);
                return null; // 丢弃该报文
            }

            if (cmdType == Tc.CmdType.SELECT) { // 选择
                // 是否接受遥控选择
                dataReactor.acceptSelect(inSlaveChannel(), infoObjAddr).whenComplete(new BiConsumer<Boolean, Throwable>() {
                    @Override
                    public void accept(Boolean isAccepted, Throwable throwable) {
                        if (throwable != null) {
                            throwable.printStackTrace();
                            return;
                        }
                        safeExecute(new Runnable() {
                            @Override
                            public void run() {
                                // 检查是否已存在
                                TcToken token = tokenMap.get(infoObjAddr);
                                if (token != null) {
                                    // 已经存在该信息体地址的遥控过程，该过程丢弃
                                    log.warn("[Tc] 遥控点{}的遥控过程已存在，放弃本次遥控选择请求", infoObjAddr);
                                    return;
                                }
                                if (isAccepted) { // 接受选择
                                    // 申请token
                                    token = tcTokenPool().applyFor(infoObjAddr, inSlaveChannel());
                                    if (token == null) {
                                        // 已经存在该信息体地址的别的遥控过程，返回否定确认
                                        iAsduSender().chSendTwoPointTcAck(infoObjAddr, cmdType, twoPointSwitch, Pn.PN_NO);
                                        return;
                                    }

                                    // 申请成功
                                    tokenMap.put(infoObjAddr, token);
                                    token.setRSelect(Tc.Type.TWO_POINT, twoPointSwitch == Tc.TwoPointSwitch.ON); // 设置为接收到遥控选择
                                    // 返回肯定确认
                                    iAsduSender().chSendTwoPointTcAck(infoObjAddr, cmdType, twoPointSwitch, Pn.PN_YES);
                                } else { // 不接受选择
                                    // 否定确认
                                    iAsduSender().chSendTwoPointTcAck(infoObjAddr, cmdType, twoPointSwitch, Pn.PN_NO);
                                }
                            }
                        });
                    }
                });
            } else { // 执行
                // 是否接受遥控执行
                dataReactor.acceptExecute(inSlaveChannel(), infoObjAddr).whenComplete(new BiConsumer<Boolean, Throwable>() {
                    @Override
                    public void accept(Boolean isAccepted, Throwable throwable) {
                        if (throwable != null) {
                            throwable.printStackTrace();
                            return;
                        }
                        safeExecute(new Runnable() {
                            @Override
                            public void run() {
                                // 检查是否存在
                                TcToken token = tokenMap.get(infoObjAddr);
                                if (token == null) {
                                    // 不存在该信息体地址的遥控过程，该过程丢弃
                                    log.warn("[Tc] 遥控点{}的遥控过程不存在，放弃本次遥控执行请求", infoObjAddr);
                                    return;
                                }
                                if (isAccepted) { // 接受执行
                                    token.setRExecute(Tc.Type.TWO_POINT, twoPointSwitch == Tc.TwoPointSwitch.ON); // 设置为接收到遥控执行
                                    // 返回肯定确认
                                    iAsduSender().chSendTwoPointTcAck(infoObjAddr, cmdType, twoPointSwitch, Pn.PN_YES).addListener(new ChannelFutureListener() {
                                        @Override
                                        public void operationComplete(ChannelFuture future) throws Exception {
                                            if (!future.isSuccess()) {
                                                future.cause().printStackTrace();
                                                return;
                                            }
                                            // 肯定确认之后，发送激活终止
                                            iAsduSender().chSendTwoPointTcFinished(infoObjAddr, twoPointSwitch);
                                        }
                                    });
                                } else { // 不接受选择
                                    // 否定确认
                                    iAsduSender().chSendTwoPointTcAck(infoObjAddr, cmdType, twoPointSwitch, Pn.PN_NO);
                                }
                            }
                        });
                    }
                });
            }
        }

        return iAsdu;
    }

    @Override
    protected IAsdu updateStateBySend(IAsdu iAsdu) throws Exception {
        Tc.Type tcType = dataReactor.getTcType();
        TypeTag tcTypeTag = tcType.getTypeTag();

        TypeTag typeTag = iAsdu.getTypeTag();
        if (tcTypeTag != typeTag) return iAsdu; // 不用处理不支持的
        Cot cot = iAsdu.getCot();
        Pn pn = cot.getPn();
        Cause cause = cot.getCause();

        if (typeTag == TypeTag.C_SC_NA_1) { // 单点遥控
            InfoObj infoObj = iAsdu.getInfoObjs().get(0);
            C_SC_NA_1_InfoElem infoElem = infoObj.getInfoElem().castTo(C_SC_NA_1_InfoElem.class);
            int infoObjAddr = infoObj.getAddr(); // 信息体地址
            Tc.OnePointSwitch onePointSwitch = infoElem.getOnePointSwitch(); // 开关状态
            Tc.CmdType cmdType = infoElem.getCmdType(); // 命令类型

            if (cause == Cause.COT_ACTCON) { // 激活确认
                if (cmdType == Tc.CmdType.SELECT) { // 选择
                    if (pn == Pn.PN_NO) return iAsdu; // 选择否定确认没有token
                    TcToken token = tokenMap.get(infoObjAddr);
                    if (token == null) {
                        log.warn("[Tc] 发送了无效的单点遥控选择肯定确认");
                        return iAsdu;
                    }
                    token.setSSelectAck(Tc.Type.ONE_POINT, pn == Pn.PN_YES); // 设置为发送了遥控确认
                    log.info("[Tc] 发送单点遥控选择肯定确认（{}）", infoObjAddr);
                    return iAsdu;
                } else { // 执行
                    TcToken token = tokenMap.get(infoObjAddr);
                    if (token == null) {
                        log.warn("[Tc] 发送了无效的单点遥控执行确认");
                        return iAsdu;
                    }
                    token.setSExecuteAck(Tc.Type.ONE_POINT, pn == Pn.PN_YES);
                    log.info("[Tc] 发送单点遥控执行{}确认（{}）", pn == Pn.PN_YES ? "肯定" : "否定", infoObjAddr);
                    return iAsdu;
                }
            } else if (cause == Cause.COT_ACTTERM) { // 激活终止
                TcToken token = tokenMap.get(infoObjAddr);
                if (token == null) {
                    log.warn("[Tc] 发送了无效的单点遥控激活终止");
                    return iAsdu;
                }
                token.setSFinished(Tc.Type.ONE_POINT);
                // 移除这个token
                tokenMap.remove(token.getAddress());
                tcTokenPool().returnBack(token);
                log.info("[Tc] 发送单点遥控激活终止（{}）", infoObjAddr);
                return iAsdu;
            }
        } else if (typeTag == TypeTag.C_DC_NA_1) { // 双点遥控
            InfoObj infoObj = iAsdu.getInfoObjs().get(0);
            C_DC_NA_1_InfoElem infoElem = infoObj.getInfoElem().castTo(C_DC_NA_1_InfoElem.class);
            int infoObjAddr = infoObj.getAddr(); // 信息体地址
            Tc.TwoPointSwitch twoPointSwitch = infoElem.getTwoPointSwitch(); // 开关状态
            Tc.CmdType cmdType = infoElem.getCmdType(); // 命令类型

            if (cause == Cause.COT_ACTCON) { // 激活确认
                if (cmdType == Tc.CmdType.SELECT) { // 选择
                    if (pn == Pn.PN_NO) return iAsdu; // 选择否定确认没有token
                    TcToken token = tokenMap.get(infoObjAddr);
                    if (token == null) {
                        log.warn("[Tc] 发送了无效的双点遥控选择肯定确认");
                        return iAsdu;
                    }
                    token.setSSelectAck(Tc.Type.TWO_POINT, pn == Pn.PN_YES); // 设置为发送了遥控确认
                    log.info("[Tc] 发送双点遥控选择肯定确认（{}）", infoObjAddr);
                    return iAsdu;
                } else { // 执行
                    TcToken token = tokenMap.get(infoObjAddr);
                    if (token == null) {
                        log.warn("[Tc] 发送了无效的双点遥控执行确认");
                        return iAsdu;
                    }
                    token.setSExecuteAck(Tc.Type.TWO_POINT, pn == Pn.PN_YES);
                    log.info("[Tc] 发送双点遥控执行{}确认（{}）", pn == Pn.PN_YES ? "肯定" : "否定", infoObjAddr);
                    return iAsdu;
                }
            } else if (cause == Cause.COT_ACTTERM) { // 激活终止
                TcToken token = tokenMap.get(infoObjAddr);
                if (token == null) {
                    log.warn("[Tc] 发送了无效的双点遥控激活终止");
                    return iAsdu;
                }
                token.setSFinished(Tc.Type.TWO_POINT);
                // 移除这个token
                tokenMap.remove(token.getAddress());
                tcTokenPool().returnBack(token);
                log.info("[Tc] 发送双点遥控激活终止（{}）", infoObjAddr);
                return iAsdu;
            }
        }

        return iAsdu;
    }

    @Nullable
    @Override
    protected AbstractStateHandler.ScheduledTask getScheduledTask() {
        return null;
    }
}
